#include "Engine.h"

Mtx Engine::GX_ModelViewMtx;

Engine::Engine() {
	fb = 0;
	background = {0, 0, 0, 0xff};
	scene = NULL;
}

Engine::~Engine() {
}

void Engine::Init() {
	InitVideo();
	InitGX();
}

void Engine::InitVideo() {
	VIDEO_Init();
	
	rmode = VIDEO_GetPreferredMode(NULL);
	
	// allocate 2 framebuffers for double buffering
	frameBuffer[0] = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
	frameBuffer[1] = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
	
	VIDEO_Configure(rmode);
	VIDEO_SetNextFramebuffer(frameBuffer[fb]);
	VIDEO_SetBlack(FALSE);
	VIDEO_Flush();
	VIDEO_WaitVSync();
	if(rmode->viTVMode&VI_NON_INTERLACE) VIDEO_WaitVSync();
}

void Engine::InitGX() {
	void *gp_fifo = NULL;
	gp_fifo = memalign(32,DEFAULT_FIFO_SIZE);
	memset(gp_fifo,0,DEFAULT_FIFO_SIZE);
	
	GX_Init(gp_fifo,DEFAULT_FIFO_SIZE);
	
	// clears the bg to color and clears the z buffer
	GX_SetCopyClear(background, 0x00ffffff);
	
	// other gx setup
	GX_SetViewport(0,0,rmode->fbWidth,rmode->efbHeight,0,1);
	yscale = GX_GetYScaleFactor(rmode->efbHeight,rmode->xfbHeight);
	xfbHeight = GX_SetDispCopyYScale(yscale);
	GX_SetScissor(0,0,rmode->fbWidth,rmode->efbHeight);
	GX_SetDispCopySrc(0,0,rmode->fbWidth,rmode->efbHeight);
	GX_SetDispCopyDst(rmode->fbWidth,xfbHeight);
	GX_SetCopyFilter(rmode->aa,rmode->sample_pattern,GX_TRUE,rmode->vfilter);
	GX_SetFieldMode(rmode->field_rendering,((rmode->viHeight==2*rmode->xfbHeight)?GX_ENABLE:GX_DISABLE));
	
	GX_SetCullMode(GX_CULL_NONE);
	GX_CopyDisp(frameBuffer[fb],GX_TRUE);
	GX_SetDispCopyGamma(GX_GM_1_0);
}

void Engine::SetRenderMode(int _render_mode) {
	render_mode = _render_mode;
	
	// Clear old vertex desc
	GX_ClearVtxDesc();
	
	if(ENGINE_MODE_3D) {
		// for 3D we need X, Y and Z
		GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
		GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
	} else {
		// for 2D we only need X and Y
		GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_F32, 0);
		GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
	}

	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
	GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT);
}

int Engine::GetRenderMode() {
	return render_mode;
}

void Engine::MainLoop() {
}

void Engine::Update() {
}

//Prepare GX to start drawing elements on the screen
void Engine::StartDrawing() {
	//Pixel Blending parameters
	GX_SetBlendMode(GX_BM_BLEND, GX_BL_ONE, GX_BL_ZERO, GX_LO_CLEAR);

	//Tev
	GX_SetNumChans(1);
	GX_SetNumTexGens(0);
	GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORDNULL, GX_TEXMAP_NULL, GX_COLOR0A0);
	GX_SetTevOp(GX_TEVSTAGE0, GX_PASSCLR);

	//Culling
	GX_SetCullMode(GX_CULL_BACK);

	//Projection
	if(scene != NULL)
	{
		//ModelView Matrix computing
		scene->GetActiveCamera()->ApplyCamera();

		//Apply result to static GX_ModelView
		guMtxIdentity(GX_ModelViewMtx);
		guMtxConcat(*(scene->GetActiveCamera()->GetViewMatrix() ), GX_ModelViewMtx, GX_ModelViewMtx);

		//Setting perspective projection mode
		guPerspective(projectionMtx, scene->GetActiveCamera()->GetFar(), 4.0f/3.0f, scene->GetActiveCamera()->GetNear(), scene->GetActiveCamera()->GetFar() );
		GX_LoadProjectionMtx(projectionMtx, GX_PERSPECTIVE);
	}
	
	//Viewport
	GX_SetViewport(0, 0, rmode->fbWidth, rmode->efbHeight, 0, 1);
	GX_SetScissor(0, 0, rmode->fbWidth, rmode->efbHeight );
}

//Finish drawing before render
void Engine::EndDrawing() {

}

void Engine::Render() {
	GX_DrawDone();
	
	fb ^= 1;		// flip framebuffer
	GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
	GX_SetColorUpdate(GX_TRUE);
	GX_CopyDisp(frameBuffer[fb],GX_TRUE);
	
	VIDEO_SetNextFramebuffer(frameBuffer[fb]);
	
	VIDEO_Flush();
	
	VIDEO_WaitVSync();
}

void Engine::SetScene(Scene* _scene) {
	scene = _scene;
}

Scene* Engine::GetScene() {
	return scene;
}
